#pragma once
#include"global_var.cpp"
#include"read_atpg.cpp"
#include"read_faults.cpp"
#include"gate.cpp"

class t_ckt
{
private:
    boost::unordered_flat_map<std::size_t, std::vector<std::tuple<char*,gate*>>,size_t_hash> pi_gate;
    std::vector<std::pair<std::size_t,char*>> po_gate; 
    std::vector<gate*> no_driven_gates;
    boost::unordered_flat_map<std::size_t,gate*,size_t_hash> local_ckt;
    boost::unordered_flat_map<std::string,gate*> local_name_gate;
    std::vector<std::vector<gate*>> t_level;
    std::vector<gate*> all_dff;
public:
    explicit t_ckt() noexcept;
    ~t_ckt() noexcept{
        for(auto& [_,ptr] : local_name_gate)
            delete ptr;
    };

    void inline en_event(gate*, std::vector<std::vector<gate*>>&, std::vector<int>&) noexcept;

    inline void en_sensitve_dff(gate*, std::vector<gate*>&, int&) noexcept;

    inline void reset() noexcept;

    inline void recover(fault*, std::vector<std::vector<gate*>>&, std::vector<int>&) noexcept;

    inline void record(fault*, std::vector<gate*>& ,int ) noexcept;

    void initial_faults(std::vector<fault*>&) noexcept;

    void fault_sim(boost::unordered_flat_map<std::size_t,std::string,size_t_hash>&,std::vector<fault*>&) noexcept;

};

t_ckt::t_ckt() noexcept
{
    local_name_gate.reserve(global_name_cell.size());
    local_ckt.reserve(local_ckt.size());

    boost::unordered_flat_map<cell*,gate*> ptr_map;
    ptr_map.reserve(global_name_cell.size());

    for(auto& [gate_name, ptr] : global_name_cell)
    {
        if(!ptr_map.contains(ptr))
            ptr_map[ptr] = new gate(*ptr);
        local_name_gate[gate_name] = ptr_map[ptr];
        auto& new_ptr = ptr_map[ptr];

        for(auto& [_1,_2,_3,vec] : ptr->output_port)
        {
            std::vector<std::tuple<char*,gate*,bool>> tmp;
            for(auto& other_ptr : vec)
            {
                gate* new_other_gate = nullptr;

                if(!ptr_map.contains(other_ptr))
                    ptr_map[other_ptr] = new gate(*other_ptr);

                new_other_gate  = ptr_map[other_ptr];

                for(auto& [_1,_2,other_wire_index,in_ptr] : other_ptr->input_port)
                {
                    if(in_ptr!=nullptr && !ptr_map.contains(in_ptr))
                        ptr_map[in_ptr] = new gate(*in_ptr);
                    if(ptr_map[in_ptr] == new_ptr) 
                        tmp.emplace_back(&(std::get<0>(new_other_gate->wire[other_wire_index])),new_other_gate,0);
                } 
            }
            new_ptr->output_to_bits.emplace_back(std::move(tmp));
        }

        int out1 = std::get<2>(ptr->output_port[0]);
        int out2 = std::get<2>(ptr->output_port.back());
        new_ptr->initial_gate(out1,out2,ptr->uninitial_para);
    }

    for(auto& [name,ptr] : global_ckt)
        local_ckt[name] = ptr_map[ptr];

    std::queue<cell*> q;
    boost::unordered_flat_set<cell*> available;
    boost::unordered_flat_set<cell*> in_queue;

    for(auto& [_,ptr] : global_ckt)
    {
        if(in_queue.count(ptr)) continue;

        bool flag = 1;
        for(auto& [_1,_2,_3,in_ptr] : ptr->input_port)
        {
            if(in_ptr == nullptr)  continue;
            flag = 0;
            break;
        }
        if(flag)
        {
            q.emplace(ptr);
            in_queue.emplace(ptr); 
            continue;
        }

        flag = 1;
        if(ptr->is_dff)
        {
            for(auto& port : ptr->async_port)
            {
                for(auto& [hashed_port,_1,_2,in_ptr] : ptr->input_port)
                    if(port == hashed_port && in_ptr != nullptr)
                        flag = 0;
            }
            if(flag)
            {
                q.emplace(ptr);
                in_queue.emplace(ptr); 
            }
        }
    }

    while(!q.empty())
    {
        std::vector<cell*> cur_level;
        while(!q.empty())
        {
            auto sp = q.front();
            cur_level.emplace_back(sp);
            in_queue.erase(sp);
            available.emplace(sp);
            q.pop();
        }
        for(auto& ptr : cur_level)
        {
            for(auto& [_1,_2,_3,output_gate_vec]: ptr->output_port)
            {
                for(auto rp_output_gate : output_gate_vec)
                {
                    if(in_queue.contains(rp_output_gate)||available.contains(rp_output_gate)) 
                        continue;

                    bool flag = 1;
                    for(auto& [port,_1,_2,rp_input_gate] : rp_output_gate->input_port)
                    {
                        if(rp_input_gate == nullptr) continue;
                        if(rp_output_gate->is_dff && rp_output_gate->async_port[0]!=port && rp_output_gate->async_port[1]!=port && rp_output_gate->async_port[2]!=port) continue;
                        if(!available.contains(rp_input_gate))
                        {
                            flag = 0;
                            break;
                        }
                    }
                    if(flag)
                    {
                        q.emplace(rp_output_gate);
                        in_queue.emplace(rp_output_gate);
                    }
                }
            }
        }
        std::vector<gate*> tmp_level(cur_level.size(),nullptr);
        for(int p = 0,sz = tmp_level.size();p < sz;++p)
            tmp_level[p] = ptr_map[cur_level[p]];
        
        t_level.emplace_back(tmp_level);
        for(auto& sp : tmp_level)
            sp->top_level = t_level.size()-1;
    }

    all_dff.reserve(20000);

    for(auto& [_,sp] : global_name_cell)
    {
        for(auto& [_1,s,index,_2] : sp->input_port)
            if(hash_input.count(s))
                pi_gate[s].emplace_back(&(std::get<0>(ptr_map[sp]->wire[index])),ptr_map[sp]);

        if(sp->is_dff) all_dff.emplace_back(ptr_map[sp]);
        if(sp->input_port.empty())
            no_driven_gates.emplace_back(ptr_map[sp]);
    }

    for(auto& po: hash_output)
    {
        cell* ptr = global_ckt[po];
        for(auto& [_1,wire_name,idx,_2] : ptr->output_port)
        {
            if(wire_name == po)
            {
                po_gate.emplace_back(po,&(std::get<0>(ptr_map[ptr]->wire[idx])));
                break;
            }
        }
    }

    for(auto& [_,ptr] : local_name_gate)
    {
        for(auto& v : ptr->output_to_bits)
            for(auto& [_,other_ptr,flag] : v)
                if(other_ptr->top_level > ptr->top_level)
                    flag = 1;
    }
}

inline void t_ckt::en_event(gate* sp, std::vector<std::vector<gate*>>& event, std::vector<int>& event_size) noexcept
{
    if(sp->in_event == 0)
    {
        auto& cur_level = sp->top_level;
        sp->in_event = 1;
        event[cur_level][event_size[cur_level]++] = sp;
    }
}

inline void t_ckt::en_sensitve_dff(gate* sp, std::vector<gate*>& sensitve_dff, int& sensitve_dff_size) noexcept
{
    if(sp->is_dff && !sp->is_sensitive_dff)
    {
        sp->is_sensitive_dff = 1;
        sensitve_dff[sensitve_dff_size++] = sp;
    }
}

inline void t_ckt::reset() noexcept
{
    for(auto& [_,ptr] : local_name_gate)
    {
        ptr->in_event = 0;
        ptr->is_sensitive_dff = 0;
        for(int i=2;i<ptr->sizeOfwire;++i)
            std::get<0>(ptr->wire[i]) = std::get<1>(ptr->wire[i]) = 'x'; 
    }   
};

inline void t_ckt::recover(fault* fault,std::vector<std::vector<gate*>>& event, std::vector<int>& event_size) noexcept
{
    if(fault->changed_dff.size() == 0) return ;

    for(int i = 0,sz = fault->changed_dff_size; i<sz; ++i)
    {
        auto& [ptr,state]  = fault->changed_dff[i];

        bool flag = 0;
        
        for(int j=2,sz=ptr->sizeOfwire;j<sz;++j)
        {
            if(std::get<1>(ptr->wire[j]) == state[j-2]) continue;
            flag = 1;
            std::get<1>(ptr->wire[j]) = state[j-2];
        }
        
        if(flag)
            en_event(ptr,event,event_size);
    }
    
    fault->changed_dff_size = 0;
}

inline void t_ckt::record(fault* fault, std::vector<gate*>& dff,int size) noexcept
{
    for(int i=0;i<size;++i)
    {
        auto& ptr = dff[i];
        auto& [ptr2 , vec] =  fault->changed_dff[fault->changed_dff_size++];
        ptr2 = ptr;
        for(int i=2,sz=ptr->sizeOfwire;i<sz;++i)
            vec[i-2] = std::get<0>(ptr->wire[i]);
    }
}

void t_ckt::initial_faults(std::vector<fault*>& faults) noexcept
{
    for(int fault_index=0,sz=faults.size();fault_index < sz;++fault_index)
    {
        auto& cur_fault = faults[fault_index];

        cur_fault->changed_dff.resize(all_dff.size(),{nullptr,std::array<char,10>{}});
        
        if(fault_index==0) continue;

        std::size_t& fe_port = cur_fault->fe_port;
        std::size_t& fe_pi_po = cur_fault->fe_pi_po;
        
        gate* fe_gate = nullptr;
        cell* fe_cell = nullptr;
        if(local_name_gate.contains(cur_fault->name))
        {
            cur_fault->fe_gate = local_name_gate[cur_fault->name];
            fe_gate = local_name_gate[cur_fault->name];
            fe_cell = global_name_cell[cur_fault->name];
        }

        
        auto& polarity = cur_fault->polarity;

        char* affected_bit = nullptr;
        std::vector<std::tuple<gate*,char*,char*,bool>> cur_affected;
        
        if(fe_gate == nullptr)
        {
            affected_bit = &(patterns[fe_pi_po][0]);
            for(auto& [bit,sp] : pi_gate[fe_pi_po]) 
                cur_affected.emplace_back(sp,bit,affected_bit,1);
            
        }
        else if(cur_fault->is_input == 0)
        {
            int idx = std::get<0>(fe_cell->output_port[0])!=fe_port;
            affected_bit = &(std::get<0>(fe_gate->wire[std::get<2>(fe_cell->output_port[idx])]));
            for(auto& [bit,sp,_] : fe_gate->output_to_bits[idx])
                cur_affected.emplace_back(sp,bit,affected_bit,0);
        }
        else
        {
            for(auto& [port,wire_name,wire_index,in_ptr] : fe_cell->input_port)
            {
                if(port != fe_port) continue;

                affected_bit = &(std::get<0>(fe_gate->wire[wire_index]));
                        
                if(in_ptr == nullptr)
                    cur_affected.emplace_back(fe_gate,affected_bit,&patterns[wire_name][0],1);
                else
                {
                    int idx = std::get<1>(in_ptr->output_port[0]) != wire_name;
                    char* source = &(std::get<0>(local_name_gate[in_ptr->name]->wire[std::get<2>(in_ptr->output_port[idx])]));
                    cur_affected.emplace_back(fe_gate,affected_bit,source,0);
                }
                break;
            }
        }

        cur_fault->affected = std::move(cur_affected);
        
    }
}

void t_ckt::fault_sim(boost::unordered_flat_map<std::size_t,std::string,size_t_hash>& patterns,std::vector<fault*>& faults)  noexcept
{
    std::vector<std::vector<gate*>> event(t_level.size());
    std::vector<int> event_size(t_level.size(),0);

    std::vector<gate*> sensitve_dff(all_dff.size(),nullptr);
    int sensitve_dff_size = 0;

    for(int i = 0 ;i<t_level.size();++i)
        event[i].resize(t_level[i].size(),nullptr);
    
    for(auto& p : no_driven_gates)
        en_event(p,event,event_size);

    initial_faults(faults);

    int sizeOfevent = event.size();
    int fault_size = faults.size();
    int all_dff_size = all_dff.size();

    for(int i=1,sz=patterns.begin()->second.size();i<sz;++i)
    {   
        for(auto& s : hash_input)
        {
            if(patterns[s][i]!=patterns[s][i-1])
                for(auto& [bit,sp] :  pi_gate[s])
                {
                    en_event(sp,event,event_size);
                    *bit = patterns[s][i];
                }
        }

        bool complete = 1;

        for(int fault_index = 0 ; fault_index<fault_size ; ++fault_index)
        {
            if(faults[fault_index]->state == 2) continue;

            auto& cur_fault = faults[fault_index];
            auto& polarity = cur_fault->polarity;
            char* affected_bit = nullptr;
            auto& affected = cur_fault->affected;

            recover(cur_fault,event,event_size);

            if(fault_index>=1)
            {
                complete = 0;
                bool flag = cur_fault->fe_gate == nullptr || cur_fault->is_input == 0;

                if(flag)
                {
                    affected_bit = std::get<2>(affected[0]);
                    if(std::get<3>(affected[0]) == 1)
                        affected_bit+=i;
                }
                else affected_bit = std::get<1>(affected[0]);

                if(*affected_bit != polarity)
                for(auto& [ptr,target,source,is_pi] : affected)
                {
                    *target = polarity;
                    en_event(ptr,event,event_size);
                }             
            }

            for(int j=0; j < sizeOfevent ; ++j)
            {
                auto& tmp = event[j];

                for(int pos = 0;pos<event_size[j];++pos)
                {
                    auto& sp = tmp[pos];
                    sp->in_event = 0;

                    if(fault_index != 0)
                        en_sensitve_dff(sp,sensitve_dff,sensitve_dff_size);
                       
                    int changed = sp->simulate();

                    for(int index = sp->out_2 != sp->out_1; changed!=0 && index >= 0 ; --index,changed = (changed>>1))
                    {
                        if(!(changed&1)) continue;
                            
                        char* changed_bit = index == 1 ? sp->out_2 : sp->out_1;

                        for(auto& [bit,other_ptr,greater_level] : sp->output_to_bits[index])
                        {
                            if(greater_level)
                                en_event(other_ptr,event,event_size);
                                
                            if(fault_index != 0)
                                en_sensitve_dff(other_ptr,sensitve_dff,sensitve_dff_size);

                            *bit = (bit == affected_bit || changed_bit == affected_bit)? polarity: *changed_bit;
                        }

                    }
                    
                }
                event_size[j] = 0;
            }

            if(fault_index>=1)
            {
                for(auto& [po_name,bit] : po_gate)
                {
                    auto& expected = vcd_outputs[po_name][i];
                    if(expected == *bit) continue;
                    else if(*bit == 'x') cur_fault->state = (cur_fault->state==0)?1:cur_fault->state;
                    else if(*bit !='x' && expected!='x')
                        cur_fault->state = 2;                      
                }
            }
            
            if(fault_index == 0)
                record(cur_fault,all_dff,all_dff_size);
            else if(cur_fault->state != 2)
            {
                record(cur_fault,sensitve_dff,sensitve_dff_size);

                for(int dff_index = 0;dff_index<sensitve_dff_size;++dff_index)
                    sensitve_dff[dff_index]->is_sensitive_dff = 0;
                
                sensitve_dff_size = 0;
            }

            if(fault_index == fault_size-1 && sensitve_dff_size != 0)
            {
                for(int dff_index = 0;dff_index<sensitve_dff_size;++dff_index)
                    sensitve_dff[dff_index]->is_sensitive_dff = 0;
                
                sensitve_dff_size = 0;
            }

            for(auto& [sp,target,source,is_pi] : affected)
            {
                en_event(sp,event,event_size);
                *target = (is_pi) ? (*(source+i)) : (*source) ;
            }
        }

        if(complete == 1) break;
    }
}



